//
// Created by konglinglong on 2020/9/8.
//

#include <sys/mman.h>
#include <fcntl.h>
#include "../../include/utils/fixRingBuffer.h"

#define FRB_ALIGN_SIZE   (16)

enum
{
    FIX_RB_DBG_CODE_BUFF_FULL,
    FIX_RB_DBG_CODE_BUFF_EMPTY,
    FIX_RB_DBG_CODE_W_CONFLICT,
    FIX_RB_DBG_CODE_R_CONFLICT,
    FIX_RB_DBG_CODE_END
};

struct FixRingBuffer
{
    uint32_t type;
    char* buffer; /* 缓存首地址 */
    uint32_t bufferSize; /* 缓存大小 */
    uint32_t isShm;
    char     shmId[128];
    uint32_t shmSize;
    uint32_t count;  /* 有效数据个数 */
    uint32_t elmntNum; /* 块个数 */
    uint32_t elmntNumMask; /* 为elmntNum-1，把%操作转换成&操作 */
    uint32_t elmntSize; /* 块大小 */
    uint32_t dataSize; /* 有效数据大小 */
    uint32_t isMp;
    uint32_t isMc;
    uint32_t wrIdx; /* 写索引 */
    uint32_t rdIdx; /* 读索引 */
    uint32_t dbgCode[FIX_RB_DBG_CODE_END]; /* 调试信息 */
};

typedef struct
{
    uint32_t check;       /* 越界检查标志 */
    uint32_t seq;         /* 序列号 */
    uint32_t actualSize; /* 实际写入大小 */
} S_FixRbElmntHdr;

static const char* gDebugCodeNameTable[] =
{
    MACRO_STRINGIFY(FIX_RB_DBG_CODE_BUFF_FULL),
    MACRO_STRINGIFY(FIX_RB_DBG_CODE_BUFF_EMPTY),
    MACRO_STRINGIFY(FIX_RB_DBG_CODE_W_CONFLICT),
    MACRO_STRINGIFY(FIX_RB_DBG_CODE_R_CONFLICT),
};
STATIC_ASSERT(ARRAY_SIZE(gDebugCodeNameTable) == FIX_RB_DBG_CODE_END);

static int32_t FixRB_Init(S_FixRingBuffer* self, char *buffer, uint32_t buffSize, uint32_t dataNum, uint32_t dataSize, uint32_t elmntSize, uint32_t type)
{
    uint32_t loop;

    if (!IS_POWER2(dataNum))
    {
        return CSP_RC_ER;
    }

    switch (type)
    {
    case FIX_RB_TYPE_NONE:
    case FIX_RB_TYPE_SPSC:
        self->isMp = 0;
        self->isMc = 0;
        break;
    case FIX_RB_TYPE_SPMC:
        self->isMp = 0;
        self->isMc = 1;
        break;
    case FIX_RB_TYPE_MPSC:
        self->isMp = 1;
        self->isMc = 0;
        break;
    case FIX_RB_TYPE_MPMC:
        self->isMp = 1;
        self->isMc = 1;
        break;
    default:
        return CSP_RC_ER;
    }

    self->type = type;
    self->buffer = buffer;
    self->bufferSize = buffSize;
    self->count = 0;
    self->elmntNum = dataNum;
    self->elmntNumMask = dataNum - 1;
    self->elmntSize = elmntSize;
    self->dataSize = dataSize;

    for (loop = 0; loop < self->elmntNum; loop++)
    {
        ((S_FixRbElmntHdr*)(self->buffer + elmntSize * loop))->check = MEM_CHECK_FLAG;
        ((S_FixRbElmntHdr*)(self->buffer + elmntSize * loop))->seq = loop;
    }

    MEMORY_BARRIER();

    return CSP_RC_OK;
}

static S_FixRingBuffer* FixRB_LcmCreate(uint32_t dataNum, uint32_t dataSize, uint32_t type)
{
    int32_t rc;
    char* bufferBase;
    char* dataBuffer;
    uint32_t dataBuffSize;
    uint32_t elmntSize;
    uint32_t totalBuffSize;
    S_FixRingBuffer* self;

    elmntSize = CALC_ALIGN_SIZE(dataSize + sizeof(S_FixRbElmntHdr), FRB_ALIGN_SIZE);
    dataBuffSize = elmntSize * dataNum;
    totalBuffSize = CALC_ALIGN_SIZE(sizeof(S_FixRingBuffer), FRB_ALIGN_SIZE) + dataBuffSize;

    bufferBase = OS_MemCalloc(1, totalBuffSize);
    if (NULL == bufferBase)
    {
        return NULL;
    }

    self = (S_FixRingBuffer*)bufferBase;
    dataBuffer = bufferBase + CALC_ALIGN_SIZE(sizeof(S_FixRingBuffer), FRB_ALIGN_SIZE);
    rc = FixRB_Init(self, dataBuffer, dataBuffSize, dataNum, dataSize, elmntSize, type);
    if (CSP_RC_OK != rc)
    {
        OS_MemFree(bufferBase);
        return NULL;
    }
    self->isShm = 0;

    return self;
}

static S_FixRingBuffer* FixRB_ShmCreate(const char* shmId, uint32_t dataNum, uint32_t dataSize, uint32_t type)
{
    int32_t rc;
    int shmFd;
    char* bufferBase;
    char* dataBuffer;
    uint32_t dataBuffSize;
    uint32_t elmntSize;
    uint32_t totalBuffSize;
    S_FixRingBuffer* self;

    elmntSize = CALC_ALIGN_SIZE(dataSize + sizeof(S_FixRbElmntHdr), FRB_ALIGN_SIZE);
    dataBuffSize = elmntSize * dataNum;
    totalBuffSize = CALC_ALIGN_SIZE(sizeof(S_FixRingBuffer), FRB_ALIGN_SIZE) + dataBuffSize;

    shmFd = shm_open(shmId, O_CREAT | O_RDWR | O_TRUNC, S_IRWXU | S_IRWXG);
    if (shmFd < 0)
    {
        return NULL;
    }
    if (ftruncate(shmFd, totalBuffSize) < 0)
    {
        shm_unlink(shmId);
        return NULL;
    }
    bufferBase = mmap(NULL, totalBuffSize, PROT_READ | PROT_WRITE, MAP_SHARED, shmFd, 0);
    if (bufferBase == (void*)-1)
    {
        shm_unlink(shmId);
        return NULL;
    }

    self = (S_FixRingBuffer*)bufferBase;
    dataBuffer = bufferBase + CALC_ALIGN_SIZE(sizeof(S_FixRingBuffer), FRB_ALIGN_SIZE);
    rc = FixRB_Init(self, dataBuffer, dataBuffSize, dataNum, dataSize, elmntSize, type);
    if (CSP_RC_OK != rc)
    {
        munmap(bufferBase, totalBuffSize);
        shm_unlink(shmId);
        return NULL;
    }
    self->isShm = 1;
    self->shmSize = totalBuffSize;
    strncpy(self->shmId, shmId, sizeof(self->shmId));

    return self;
}

S_FixRingBuffer* FixRB_Create(const char* shmId, uint32_t dataNum, uint32_t dataSize, uint32_t type)
{
    if (shmId)
    {
        return FixRB_ShmCreate(shmId, dataNum, dataSize, type);
    }
    else
    {
        return FixRB_LcmCreate(dataNum, dataSize, type);
    }
}

void FixRB_Destroy(S_FixRingBuffer* self)
{
    char shmId[128];

    if (self->isShm)
    {
        strncpy(shmId, self->shmId, sizeof(self->shmId));
        munmap(self, self->shmSize);
        shm_unlink(shmId);
    }
    else
    {
        OS_MemFree(self);
    }
}

void* FixRB_WriteRequest(S_FixRingBuffer* self, uint32_t size)
{
    int32_t ret;
    uint32_t currWrIdx;
    uint32_t nextWrIdx;
    S_FixRbElmntHdr* pCurrWrElmnt;

    MACRO_ASSERT(size <= self->dataSize);

    while (1)
    {
        currWrIdx = self->wrIdx;
        nextWrIdx = currWrIdx + 1;

        pCurrWrElmnt = (S_FixRbElmntHdr*)(self->buffer + self->elmntSize * (currWrIdx & self->elmntNumMask));
        if (unlikely(pCurrWrElmnt->seq != currWrIdx))
        {
            if (currWrIdx != READ_ONCE(self->wrIdx))
            {
                CSP_ADD_DBG_CODE(self, FIX_RB_DBG_CODE_W_CONFLICT);
                continue;
            }
            CSP_ADD_DBG_CODE(self, FIX_RB_DBG_CODE_BUFF_FULL);
            return NULL;
        }

        if (self->isMp)
        {
            ret = COMPARE_AND_SWAP(&self->wrIdx, currWrIdx, nextWrIdx);
            if (unlikely(!ret))
            {
                continue;
            }
        }
        else
        {
            self->wrIdx = nextWrIdx;
        }

        MACRO_ASSERT(MEM_CHECK_FLAG == pCurrWrElmnt->check);

        pCurrWrElmnt->actualSize = size;
        return (char*)pCurrWrElmnt + sizeof(S_FixRbElmntHdr);
    }
}

int32_t FixRB_WriteCommit(S_FixRingBuffer* self, void* chunk)
{
    S_FixRbElmntHdr* pElmnt;

    pElmnt = (S_FixRbElmntHdr*)((char*)chunk - sizeof(S_FixRbElmntHdr));

    if (FIX_RB_TYPE_NONE != self->type)
    {
        FETCH_AND_ADD(&self->count, 1);
        MEMORY_BARRIER();
    }
    else
    {
        self->count++;
    }

    pElmnt->seq++;

    return CSP_RC_OK;
}

void* FixRB_ReadRequest(S_FixRingBuffer* self, uint32_t* size)
{
    int32_t ret;
    uint32_t currRdIdx;
    uint32_t nextRdIdx;
    S_FixRbElmntHdr* pCurrRdElmnt;

    while (1)
    {
        currRdIdx = self->rdIdx;
        nextRdIdx = currRdIdx + 1;

        pCurrRdElmnt = (S_FixRbElmntHdr*)(self->buffer + self->elmntSize * (currRdIdx & self->elmntNumMask));
        if (unlikely((pCurrRdElmnt->seq) != nextRdIdx))
        {
            if (currRdIdx != READ_ONCE(self->rdIdx))
            {
                CSP_ADD_DBG_CODE(self, FIX_RB_DBG_CODE_R_CONFLICT);
                continue;
            }
            CSP_ADD_DBG_CODE(self, FIX_RB_DBG_CODE_BUFF_EMPTY);
            return NULL;
        }

        if (self->isMc)
        {
            ret = COMPARE_AND_SWAP(&self->rdIdx, currRdIdx, nextRdIdx);
            if (unlikely(!ret))
            {
                continue;
            }
        }
        else
        {
            self->rdIdx = nextRdIdx;
        }

        MACRO_ASSERT(MEM_CHECK_FLAG == pCurrRdElmnt->check);

        *size = pCurrRdElmnt->actualSize;
        return (char*)pCurrRdElmnt + sizeof(S_FixRbElmntHdr);
    }
}

int32_t FixRB_ReadConsume(S_FixRingBuffer* self, void* chunk)
{
    S_FixRbElmntHdr* pElmnt;

    pElmnt = (S_FixRbElmntHdr*)((char*)chunk - sizeof(S_FixRbElmntHdr));

    if (FIX_RB_TYPE_NONE != self->type)
    {
        FETCH_AND_SUB(&self->count, 1);
        MEMORY_BARRIER();
    }
    else
    {
        self->count--;
    }

    pElmnt->seq += self->elmntNum - 1;

    return CSP_RC_OK;
}

int32_t FixRB_Put(S_FixRingBuffer* self, void* data, uint32_t size)
{
    void* chunk;

    chunk = FixRB_WriteRequest(self, size);
    if (unlikely(NULL == chunk))
    {
        return CSP_RC_ER;
    }
    memcpy(chunk, data, size);
    return FixRB_WriteCommit(self, chunk);
}

int32_t FixRB_Get(S_FixRingBuffer* self, void* buffer, uint32_t* size)
{
    void* chunk;
    uint32_t chunkSize;

    chunk = FixRB_ReadRequest(self, &chunkSize);
    if (unlikely(NULL == chunk || *size < chunkSize))
    {
        return CSP_RC_ER;
    }
    *size = chunkSize;
    memcpy(buffer, chunk, chunkSize);
    return FixRB_ReadConsume(self, chunk);
}

uint32_t FixRB_Count(S_FixRingBuffer* self)
{
    return self->count;
}

void FixRB_Dump(S_FixRingBuffer* self, void* buffer, uint32_t size)
{
    uint32_t loop;
    int32_t offset;

    offset = snprintf(buffer, size, "FIX RING BUFFER<type:%u> : \n", self->type);
    if (offset >= size)
    {
        return;
    }
    for (loop = 0; loop < ARRAY_SIZE(gDebugCodeNameTable); loop++)
    {
        offset += snprintf((char*)buffer + offset, size - offset, "%s : %u\n",
            gDebugCodeNameTable[loop], self->dbgCode[loop]);
        if (offset >= size)
        {
            break;
        }
    }
}


